#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
#include <linux/pinctrl/consumer.h>
#include <linux/gpio.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <hc_clk_gate.h>

#include "spi_reg_struct.h"

#define TMOD_TX_AND_RX 				0x00 << 10
#define MOTOROLA_SPI				0x00 << 4
#define SCPOL_HIGH				0x01 << 6
#define SCPH_HIGH				0x01 << 6
#define SCPOL_LOW				0x00 << 6
#define SCPH_LOW				0x00 << 6
#define DFS_8BIT				0x07 << 0

#define FIFO_HALF				0x08

#define DMA_TIMEOUT				msecs_to_jiffies(10000)
#define DMA_LEN_MIN				(0x100)

struct hc_spim_priv {
	int			irq;
	int			err;

	uint32_t		dma;
	uint32_t		clk;
	uint32_t		sclk;
	uint32_t		mode;

	void __iomem		*base;

	struct pinctrl		*pctl;
	struct pinctrl_state	*active;

	struct spi_device	*spi;
	struct platform_device	*pdev;

	struct completion	completion;

	uint32_t 		num_cs;
	struct gpio_desc        *cs_padctl;
};

static irqreturn_t hc_spi_interrupt(int dummy, void *param)
{
	struct hc_spim_priv *priv	= (struct hc_spim_priv *)param;
	spi_reg_t *reg 			= (spi_reg_t *)priv->base;
	uint32_t temp;

	temp = (reg->msticr.val >> 5) & 0x1;
	complete(&priv->completion);

	return IRQ_HANDLED;
}

static void hc_set_cs(struct spi_device *spi, bool enable)
{
	struct hc_spim_priv *priv = spi_master_get_devdata(spi->master);

	gpiod_set_value(priv->cs_padctl, enable);

	return;
}

static void hc_spi_init(struct platform_device *pdev,
			struct hc_spim_priv *priv)
{
	spi_reg_t *reg = (spi_reg_t *)priv->base;
	
	reg->ssienr.ssi_en = 0x00;

	gpiod_set_value(priv->cs_padctl, 1);
	
	reg->ctrl0.val = TMOD_TX_AND_RX | MOTOROLA_SPI | DFS_8BIT;	

	switch (priv->mode) {
	case 0:
		reg->ctrl0.val |= SCPH_LOW  | SCPOL_LOW;
		break;
	case 1:
		reg->ctrl0.val |= SCPH_HIGH | SCPOL_LOW;
		break;
	case 2:
		reg->ctrl0.val |= SCPH_LOW  | SCPOL_HIGH;
		break;
	case 3:
		reg->ctrl0.val |= SCPH_HIGH | SCPOL_HIGH;
		break;
	default:
		dev_err(&pdev->dev, "spi mode select error\n");
		break;
	}

	reg->baudr.sckdv	= priv->clk;
	reg->txftlr.tft		= 0x01;
	reg->rxftlr.rft		= 0x00;

	reg->imr.val		= 0x00;
	reg->ser.ser		= 0x01;
	reg->mvcr.val		= 0x00;

	reg->spi_lsbf.val	= 0x00;

	reg->ssienr.ssi_en	= 0x01;
}

static int hc_spi_busy(struct hc_spim_priv* priv)
{
	unsigned long timeout_jiffies = 10000;
	unsigned long deadline;
	int timeout = 0;
	spi_reg_t *reg = (spi_reg_t *)priv->base;

	deadline = jiffies + timeout_jiffies;
	while (!timeout) {
		if (time_after_eq(jiffies, deadline))
			timeout = 1;

		if (reg->sr.busy == 0)
			return 0;

		cond_resched();
	}

	dev_err(&priv->pdev->dev, "spim busy time out\n");

	return -ETIMEDOUT;

}

static int hc_spi_fifo_ready(struct hc_spim_priv* priv, int fifo_status)
{
	unsigned long timeout_jiffies = 1000;
	unsigned long deadline;
	int timeout = 0;
	spi_reg_t *reg = (spi_reg_t *)priv->base;

	deadline = jiffies + timeout_jiffies;
	while (!timeout) {
		if (time_after_eq(jiffies, deadline))
			timeout = 1;

		if (reg->sr.tfnf == 1 && fifo_status == 0)
			return 0;

		if (reg->sr.rfne == 1 && fifo_status == 1)
			return 0;

		cond_resched();
	}

	if (fifo_status)
		dev_err(&priv->pdev->dev, "rx fifo empty time out\n");
	else
		dev_err(&priv->pdev->dev, "tx fifo full time out\n");

	return -ETIMEDOUT;
}

static void hc_spi_pio_tx(struct hc_spim_priv *priv, uint8_t *buf, uint32_t len)
{
	uint32_t i, j = 0;
	spi_reg_t *reg = (spi_reg_t *)priv->base;

	reg->ssienr.ssi_en = 0x00;
	reg->ctrl1.ndf = len % FIFO_HALF - 1;
	reg->ctrl0.tmod = 0x01; 
	reg->ssienr.ssi_en = 0x01;
	for (i = 0; i < (len % FIFO_HALF); i++) {
		if (hc_spi_fifo_ready(priv, 0) != 0) {
			priv->err = -1;
			return;
		}
		reg->dr_low[0] = buf[i];
	}
	hc_spi_busy(priv);

	reg->ssienr.ssi_en = 0x00;
	reg->ctrl1.ndf = FIFO_HALF - 1;
	reg->ctrl0.tmod = 0x01; 
	reg->ssienr.ssi_en = 0x01;
	for (; i < len; i++, j++) {
		if (j % FIFO_HALF == 0)
			hc_spi_busy(priv);
			
		if (hc_spi_fifo_ready(priv, 0) != 0) {
			priv->err = -1;
			return;
		}
		reg->dr_low[0] = buf[i];
	}
}

static void hc_spi_pio_rx(struct hc_spim_priv *priv, uint8_t *buf, uint32_t len)
{
	uint32_t i = 0;
	spi_reg_t *reg = (spi_reg_t *)priv->base;

	reg->ssienr.ssi_en = 0x00;
	reg->ctrl1.ndf = FIFO_HALF - 1;
	reg->ctrl0.tmod = 0x02;
	reg->rx_smp_dly.val = 0x01;
	reg->ssienr.ssi_en = 0x01;
	while (len > FIFO_HALF) {
		reg->dr_low[0] = 0x00;
		for (i = 0; i < FIFO_HALF; i++) {
			if (hc_spi_fifo_ready(priv, 0) != 0) {
				priv->err = -1;
				return;
			}
			buf[i] = reg->dr_low[0];
		}

		buf += FIFO_HALF;
		len -= FIFO_HALF;

		hc_spi_busy(priv);
	}

	reg->ssienr.ssi_en = 0x00;
	reg->ctrl1.ndf = len - 1;
	reg->ctrl0.tmod = 0x02;
	reg->rx_smp_dly.val = 0x00;
	reg->ssienr.ssi_en = 0x01;
	reg->dr_low[0] = 0x00;
	for (i = 0; i < len; i++) {
		if (hc_spi_fifo_ready(priv, 0) != 0) {
			priv->err = -1;
			return;
		}
		buf[i] = reg->dr_low[0];
	}
}

void hc_spi_dma_tx(struct hc_spim_priv *priv, void *buf, unsigned len)
{
	dma_addr_t tx_dma;
	spi_reg_t *reg = (spi_reg_t *)priv->base;

	tx_dma = dma_map_single(&priv->pdev->dev, buf, len, DMA_TO_DEVICE);
	if (dma_mapping_error(&priv->pdev->dev, tx_dma)) {
		dev_err(&priv->pdev->dev, "DMA map tx fail!\n");
		return;
	}

	init_completion(&priv->completion);

	reg->ssienr.ssi_en	= 0x00;
	reg->ctrl0.val		= 0x107;
	reg->ctrl1.ndf		= len - 1;
	reg->dmamwar		= tx_dma & 0x0fffffff;
	reg->dmamrar		= tx_dma & 0x0fffffff;
	reg->imr.val		= 0x20;
	reg->ssienr.ssi_en	= 0x01;
	reg->dmacr.dmacr	= 0x01;

	if (wait_for_completion_timeout(&priv->completion, DMA_TIMEOUT) == 0) {
		dev_err(&priv->pdev->dev, "DMA tx time out!\n");
	}
		dma_unmap_single(&priv->pdev->dev, tx_dma, len, DMA_TO_DEVICE);
	
	return;
}

void hc_spi_dma_rx(struct hc_spim_priv *priv, void *buf, unsigned len)
{
	dma_addr_t rx_dma;
	spi_reg_t *reg = (spi_reg_t *)priv->base;

	rx_dma = dma_map_single(&priv->pdev->dev, buf, len, DMA_FROM_DEVICE);
	if (dma_mapping_error(&priv->pdev->dev, rx_dma)) {
		dev_err(&priv->pdev->dev, "DMA map rx fail!\n");
		return;
	}

	init_completion(&priv->completion);

	reg->ssienr.ssi_en	= 0x00;
	reg->ctrl0.val		= 0x207;
	reg->rx_smp_dly.val	= 0x03;
	reg->imr.val		= 0x20;
	reg->ctrl1.ndf		= len - 1;
	reg->dmamwar		= rx_dma & 0x0fffffff;
	reg->ssienr.ssi_en	= 0x01;
	reg->dmacr.dmacr	= 0x01;

	if (wait_for_completion_timeout(&priv->completion, DMA_TIMEOUT) == 0) {
		dev_err(&priv->pdev->dev, "DMA rx time out!\n");
	}

	dma_unmap_single(&priv->pdev->dev, rx_dma, len, DMA_FROM_DEVICE);

	return;
}

static void hc_spi_transfer_tx(struct hc_spim_priv *priv,
			       void *buf, 
			       unsigned len)
{
	if (!priv->dma|| len < DMA_LEN_MIN) {
		hc_spi_pio_tx(priv, (uint8_t *)buf, len);
		return;
	} else {
		hc_spi_dma_tx(priv, buf, len);
		return;
	}
}

static void hc_spi_transfer_rx(struct hc_spim_priv *priv,
			       void *buf,
			       unsigned len)
{
	if (!priv->dma || len < DMA_LEN_MIN) {
		hc_spi_pio_rx(priv, (uint8_t *)buf, len);
		return;
	} else {
		hc_spi_dma_rx(priv, buf, len);
		return;
	}
}

static int hc_spi_transfer_one(struct spi_master *master,
			       struct spi_device *spi,
			       struct spi_transfer *xfer)
{
	size_t len = xfer->len;
	uint8_t *rx_buf = xfer->rx_buf;
	uint8_t *tx_buf = &((uint8_t *)xfer->tx_buf)[0];
	struct hc_spim_priv *priv = spi_master_get_devdata(master);

	if (xfer->tx_buf != NULL && xfer->len != 0) 
		hc_spi_transfer_tx(priv, (void *)tx_buf, len);	

	if (xfer->rx_buf != NULL && xfer->len != 0)
		hc_spi_transfer_rx(priv, (void *)rx_buf, len);

	hc_spi_busy(priv);
	return 0;
}

static int hc_spi_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct spi_master *master;
	struct hc_spim_priv *priv;
	struct resource *res;
	const char *buf;
	int ret = 0;
	
	hc_clk_enable(SPI_MASTER_CLK);

	of_property_read_string(pdev->dev.of_node, "status", &buf);
	if (strcmp("okay", buf))
		return -1;
	
	master = spi_alloc_master(&pdev->dev, sizeof(*priv));
	if (!master)
		return -ENOMEM;

	master->dev.of_node = pdev->dev.of_node;
	master->bus_num = 0;
	master->num_chipselect = 3;
	master->mode_bits = SPI_TX_DUAL;
	master->bits_per_word_mask = SPI_BPW_MASK(8);
	master->flags = SPI_MASTER_MUST_TX;
	master->transfer_one = hc_spi_transfer_one;
	master->set_cs = hc_set_cs;

	priv = spi_master_get_devdata(master);

	priv->pdev = pdev;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(dev, "not found <reg> of sfiobase!\n");
		ret = -ENXIO;
		goto err_put_master;
	}

	priv->base = ioremap(res->start, res->end - res->start + 1);
	if (IS_ERR(priv->base)) {
		dev_err(dev, "fail ioremap <reg> of sfiobase!\n");
		ret = -ENXIO;
		goto err_put_master;
	}

	priv->pctl = devm_pinctrl_get(dev);
	if (IS_ERR(priv->pctl)) {
		ret = -ENXIO;
		goto err_put_master;
	}
	
	priv->active = pinctrl_lookup_state(priv->pctl, "active");
	if (IS_ERR(priv->active)) {
		devm_pinctrl_put(priv->pctl);
		ret = -ENXIO;
		goto err_put_master;
	}

	if (pinctrl_select_state(priv->pctl, priv->active)) {
		devm_pinctrl_put(priv->pctl);
		ret = -ENXIO;
		goto err_put_master;
	}

	priv->cs_padctl = devm_gpiod_get_optional(dev, "cs-pin", GPIOD_OUT_HIGH);
	if (IS_ERR(priv->cs_padctl)) {
		ret = PTR_ERR(priv->cs_padctl);
		if (ret != -EPROBE_DEFER)
			dev_err(dev, "failed to acquire cs gpio");
		ret = -ENXIO;
		goto err_put_master;
	}

	ret = of_property_read_u32(pdev->dev.of_node, "transfer-mode", &priv->mode);
	if (ret < 0) {
		priv->mode = 0;
	}

	ret = of_property_read_u32(pdev->dev.of_node, "clk_ratio", &priv->clk);
	if (ret < 0) {
		priv->clk = 6;	
	}

	ret = of_property_read_u32(pdev->dev.of_node, "dma-mode", &priv->dma);
	if (ret < 0) {
		priv->dma = 0;
	}

	if (priv->dma) {
		init_completion(&priv->completion);

		priv->irq = platform_get_irq(pdev, 0);
		if (priv->irq < 0) {
			dev_err(dev, "not found <interrupts>!\n");
			ret = -ENXIO;
			goto err_put_master;
		}
	
		ret = request_irq(priv->irq, hc_spi_interrupt, 0,
				  dev_name(dev), priv);
		if (ret != 0) {
			dev_err(dev, "error request irq %d!\n", 
				priv->irq);
			goto err_put_master;
		}

		if (priv->irq < 0) {
			dev_err(dev, "not fonud <interrupts>!\n");
				ret = -ENXIO;
				goto err_put_master;
		}
	}

	hc_spi_init(pdev,priv);

	platform_set_drvdata(pdev, priv);

	ret = devm_spi_register_master(dev, master);
	if (ret) {
		dev_err(dev, "faild to register SPI master\n");
		goto err_free_irq;
	}

	return 0;

err_free_irq:
	free_irq(priv->irq, dev);

err_put_master:
	spi_master_put(master);

	return ret;
}

static int hc_spi_remove(struct platform_device *pdev)
{
        return 0;
}

static const struct of_device_id hichip_spi_of_matches[] = {
        { .compatible = "hichip,hc16xx-spi" },
        {},
};
MODULE_DEVICE_TABLE(of, hichip_spi_of_matches);

static struct platform_driver hichip_spi_driver = {
        .probe          = hc_spi_probe,
        .remove         = hc_spi_remove,
        .driver         = {
                .name           = "hc16xx-spi",
                .of_match_table = hichip_spi_of_matches,
        },
};

module_platform_driver(hichip_spi_driver);

MODULE_LICENSE("GPL");
MODULE_ALIAS("platform: hc-spi");
MODULE_AUTHOR("Jerry wang <jerry.wang@hichiptech.com");
MODULE_DESCRIPTION("HiChip spi controller driver");
